﻿using System;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
using System.Dynamic;
using Microsoft.Scripting;
using Microsoft.Scripting.Actions;
using Microsoft.Scripting.Actions.Calls;
using Microsoft.Scripting.Runtime;

namespace Cjc.Calculation.ExpressionEngine.Excel.Runtime.Binding
{
	public class ExcelCallAction<THelper> : InvokeMemberBinder
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="ExcelCallAction&lt;THelper&gt;"/> class.
		/// </summary>
		/// <param name="name">The name of the member to invoke.</param>
		/// <param name="ignoreCase">true if the name should be matched ignoring case; false otherwise.</param>
		/// <param name="arguments">The signature of the arguments at the call site.</param>
		public ExcelCallAction( string name, bool ignoreCase, CallInfo callInfo )
			: base( name, ignoreCase, callInfo )
		{
		}

		/// <summary>
		/// Fallbacks the invoke.
		/// </summary>
		/// <param name="self">The self.</param>
		/// <param name="args">The args.</param>
		/// <param name="onBindingError">The on binding error.</param>
		/// <returns></returns>
		public override DynamicMetaObject FallbackInvoke( DynamicMetaObject self, DynamicMetaObject[] args, DynamicMetaObject onBindingError )
		{
			DynamicMetaObject result = TryBind( this, self, args );
			if ( result == null ) throw new NotImplementedException();
			return result;
		}

		/// <summary>
		/// Fallbacks the invoke member.
		/// </summary>
		/// <param name="self">The self.</param>
		/// <param name="args">The args.</param>
		/// <param name="onBindingError">The on binding error.</param>
		/// <returns></returns>
		public override DynamicMetaObject FallbackInvokeMember( DynamicMetaObject self, DynamicMetaObject[] args, DynamicMetaObject onBindingError )
		{
			DynamicMetaObject result = TryBind( this, self, args );
			if ( result == null ) throw new NotImplementedException();
			return result;
		}

		/// <summary>
		/// Tries the bind.
		/// </summary>
		/// <param name="binder">The binder.</param>
		/// <param name="target">The target.</param>
		/// <param name="args">The args.</param>
		/// <returns></returns>
		public static DynamicMetaObject TryBind( InvokeMemberBinder binder, DynamicMetaObject target, DynamicMetaObject[] args )
		{
            var scopelan = target.Value as ScopeAndLanguage;
            var context = scopelan.LanguageContext as ExcelContext;
            var excelBinder = context.Binder as ExcelBinder;

            var paramBinder = new ExcelOverloadResolver(excelBinder, args.ToList(), new CallSignature(args.Count()), Expression.Constant(scopelan.Scope)); 

			var methodGroup = ReflectionCache.GetMethodGroup(
				typeof( THelper ),
				binder.Name,
				BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy,
				( m, c ) => m.Name.Equals( binder.Name, StringComparison.InvariantCultureIgnoreCase ) );

			if ( methodGroup == null )
			{
				return ExcelBinder.MakeError(
                    excelBinder.MakeMissingMemberError(typeof(THelper), target, binder.Name),
                    BindingRestrictions.Empty,
                    typeof(THelper));
            }

            var binding = excelBinder.CallMethod(
                paramBinder,
                methodGroup.Methods.Select(t => t.Method).Cast<MethodBase>().ToList());

            // If the expression does not return object, throw in a conversion
            if (!ExcelOperationBinder.AreReferenceAssignable(typeof(object), binding.Expression.Type))
                binding = binding.BindConvert(new ExcelConversionBinder(excelBinder, typeof(object), ConversionResultKind.ExplicitCast));

            return binding;
        }

		/// <summary>
		/// Makes a conditional expression.
		/// </summary>
		/// <param name="target">The target.</param>
		/// <param name="args">The arguments.</param>
		/// <returns></returns>
		private DynamicMetaObject MakeIf( DynamicMetaObject target, DynamicMetaObject[] args )
		{
            var scopelan = target.Value as ScopeAndLanguage;
            var context = scopelan.LanguageContext as ExcelContext;
            var binder = context.Binder as ExcelBinder;

			var type = ( binder.PreferConvert( args[ 1 ].LimitType, args[ 2 ].LimitType ) == Candidate.One )
				? args[ 1 ].LimitType
				: args[ 2 ].LimitType;

			return new DynamicMetaObject(
				Expression.Condition(
					binder.ConvertTo( typeof( bool ), ConversionResultKind.ImplicitCast, args[ 0 ] ).Expression,
					binder.ConvertTo( type, ConversionResultKind.ImplicitCast, args[ 1 ] ).Expression,
					binder.ConvertTo( type, ConversionResultKind.ImplicitCast, args[ 2 ] ).Expression ),
				BindingRestrictions.Empty );
		}
	}
}